package com.ruoyi.web.controller.miniprogram;

import com.ruoyi.common.config.EnvConfig;
import com.ruoyi.common.constant.CacheConstants;
import com.ruoyi.common.constant.OperationType;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.core.domain.BaseOrder;
import com.ruoyi.common.core.domain.dto.SysOrderDto;
import com.ruoyi.common.core.domain.entity.socket.bean.PayStatusInfo;
import com.ruoyi.common.core.domain.entity.system.*;
import com.ruoyi.common.core.domain.entity.wsbean.JishiOrderEndChargeMsg;
import com.ruoyi.common.core.domain.entity.wsbean.OrderBeginChargeMsg;
import com.ruoyi.common.core.domain.entity.wsbean.TaocanOrderEndChargeMsg;
import com.ruoyi.common.core.domain.model.CreatePintuanOrderReqBody;
import com.ruoyi.common.core.domain.model.LoginUser;
import com.ruoyi.common.core.domain.model.WxRefundReqBody;
import com.ruoyi.common.enums.*;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.CacheUtils;
import com.ruoyi.common.utils.CoordinateCommonUtil;
import com.ruoyi.common.utils.CoordinateTransformUtil;
import com.ruoyi.common.utils.bean.BeanUtils;
import com.ruoyi.common.utils.spring.SpringUtils;
import com.ruoyi.handler.websocket.WebSocketMessageHandler;
import com.ruoyi.manager.SocketChannelManager;
import com.ruoyi.manager.WebSocketChannelManager;
import com.ruoyi.pay.service.IWxPayV3Service;
import com.ruoyi.system.service.*;
import io.netty.channel.Channel;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.web.bind.annotation.*;

import java.math.BigDecimal;
import java.text.SimpleDateFormat;
import java.util.*;

@Slf4j
@RestController
@RequestMapping("/miniprogram/v1/order")
public class MiniOrderController extends BaseController {

    @Autowired
    private ISysOrderService sysOrderService;
    @Autowired
    private IWxPayV3Service wxPayV3Service;

    @Autowired
    private ISysJishiService sysJishiService;

    @Autowired
    private ISysJishiOrderService sysJishiOrderService;

    @Autowired
    private ISysTaocanOrderService sysTaocanOrderService;

    @Autowired
    private ISysPintuanChildrenOrderService sysPintuanChildrenOrderService;

    @Autowired
    private ISysPintuanParentOrderService sysPintuanParentOrderService;

    @Autowired
    private ISysBoatService sysBoatService;

    @Autowired
    private ISysTaocanService sysTaocanService;

    @Autowired
    private ISysScenicService sysScenicService;

    @Autowired
    private ISysWharfService sysWharfService;

    private SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd HH:mm");

    @Autowired
    private IUserBoatOrderInfoService userBoatOrderInfoService;


    @Data
    static class CreateJishiOrderReqBody {
        /**
         * 景区id。
         */
        private Long scenicId;
        /**
         * 码头id。
         */
        private Long wharfId;
        /**
         * 船只id。
         */
        private Long boatId;
        /**
         * 计时收费规则id。
         */
        private Integer jishiId;
        /**
         * 小程序用户当前位置坐标的经度。保留6位小数，示例：114.344760
         */
        private Double longitude;
        /**
         * 小程序用户当前位置坐标的纬度。保留6位小数，示例：30.498204
         */
        private Double latitude;
    }

    @Data
    static class CreateTaocanOrderReqBody {
        /**
         * 景区id。
         */
        private Long scenicId;
        /**
         * 码头id。
         */
        private Long wharfId;
        /**
         * 船只id。
         */
        private Long boatId;
        /**
         * 套餐id。
         */
        private Long taocanId;
        /**
         * 小程序用户当前位置坐标的经度。保留6位小数，示例：114.344760
         */
        private Double longitude;
        /**
         * 小程序用户当前位置坐标的纬度。保留6位小数，示例：30.498204
         */
        private Double latitude;
    }

    /**
     * 检查当前用户是否满足创建新订单的条件。
     */
    private void confirmIfAllowedCreateNewOrder() {
        LoginUser loginUser = getLoginUser();
        Long userId = loginUser.getUserId();

        UserBoatOrderInfo userBoatOrderInfo = userBoatOrderInfoService.selectUserBoatOrderInfoByUserId(userId);
        if (Objects.nonNull(userBoatOrderInfo)) {
            //订单状态。0：待付款，1：待使用，2：使用中，3: 已关闭，4：已完成
            int orderStatus = userBoatOrderInfo.getOrderStatus();
            if (orderStatus == 0) {
                throw new ServiceException("存在待付款的订单，请先完成支付后再创建新订单!", 403);
            } else if (orderStatus == 1) {
                throw new ServiceException("存在待使用的订单，请使用完成后再创建新订单!", 404);
            } else if (orderStatus == 2) {
                throw new ServiceException("存在使用中的订单，请使用完成后再创建新订单!", 405);
            }
        } else { //即使boatOrderInfo为空也有可能存在已结束，但还未支付订单费用的计时计费订单和还未支付超时费用的套餐订单。
            //先检查是否存在待支付的计时计费订单
            SysJishiOrder unpaidJishiOOrder = sysJishiOrderService.queryUnpaidOrder(userId);
            if (Objects.nonNull(unpaidJishiOOrder)) {
                throw new ServiceException("存在待付款的订单，请先完成支付后再创建新订单!", 403);
            } else {
                SysTaocanOrder unpaidTaocanOrder = sysTaocanOrderService.queryUnpaidOrder(userId);
                if (Objects.nonNull(unpaidTaocanOrder)) {
                    throw new ServiceException("存在待付款的订单，请先完成支付后再创建新订单!", 403);
                }
            }
        }

        //先检查是否存在待支付的订单
//        SysJishiOrder unpaidJishiOOrder = sysJishiOrderService.queryUnpaidOrder(userId);
//        if (Objects.nonNull(unpaidJishiOOrder)) {
//            throw new ServiceException("存在待支付的计时计费订单，请先完成支付!");
//        }
//        SysTaocanOrder unpaidTaocanOrder = sysTaocanOrderService.queryUnpaidOrder(userId);
//        if (Objects.nonNull(unpaidTaocanOrder)) {
//            throw new ServiceException("存在待支付的套餐计费订单，请先完成支付!");
//        }
//        SysPintuanChildrenOrder unpaidPintuanOrder = sysPintuanChildrenOrderService.queryUnpaidOrder(userId);
//        if (Objects.nonNull(unpaidPintuanOrder)) {
//            throw new ServiceException("存在待支付的拼团订单，请先完成支付!");
//        }
//
//        //再检查是否存在待使用的订单
//        SysJishiOrder toUseJishiOrder = sysJishiOrderService.queryToUseOrder(userId);
//        if (Objects.nonNull(toUseJishiOrder)) {
//            throw new ServiceException("存在待使用的计时计费订单，请先使用完成后再继续下单!");
//        }
//        SysTaocanOrder toUseTaocanOrder = sysTaocanOrderService.queryToUseOrder(userId);
//        if (Objects.nonNull(toUseTaocanOrder)) {
//            throw new ServiceException("存在待使用的套餐计费订单，请先使用完成后再继续下单!");
//        }
//        SysPintuanChildrenOrder toUsePintuanOrder = sysPintuanChildrenOrderService.queryToUseOrder(userId);
//        if (Objects.nonNull(toUsePintuanOrder)) {
//            throw new ServiceException("存在待使用的拼团订单，请先使用完成后再继续下单!");
//        }
//
//        //再检查是否存在使用中的订单
//        SysJishiOrder usingJishiOrder = sysJishiOrderService.queryUsingOrder(userId);
//        if (Objects.nonNull(usingJishiOrder)) {
//            throw new ServiceException("存在使用中的计时计费订单，请先使用完成后再继续下单!");
//        }
//        SysTaocanOrder usingTaocanOrder = sysTaocanOrderService.queryUsingOrder(userId);
//        if (Objects.nonNull(usingTaocanOrder)) {
//            throw new ServiceException("存在使用中的套餐计费订单，请先使用完成后再继续下单!");
//        }
//        SysPintuanChildrenOrder usingPintuanOrder = sysPintuanChildrenOrderService.queryUsingOrder(userId);
//        if (Objects.nonNull(usingPintuanOrder)) {
//            throw new ServiceException("存在使用中的拼团订单，请先使用完成后再继续下单!");
//        }
    }

    /**
     * 创建计时计费订单。
     *
     * @return
     */
    @PostMapping("/createJishiOrder")
    public AjaxResult createJishiOrder(@RequestBody CreateJishiOrderReqBody reqBody) {
        log.info("createJishiOrder reqBody: {}", reqBody);

        SysWharf sysWharf = sysWharfService.selectWharfByWharfId(reqBody.getWharfId());
        //检查确认用户下单时所在位置距离所选码头的直线距离是否在指定范围内
//        confirmUserDistance(reqBody.getLongitude(), reqBody.getLatitude(), sysWharf);

        //检查当前用户名下是否存在未完成(含待付款，待使用，使用中状态)的订单。
        confirmIfAllowedCreateNewOrder();

        //检查当前所选船只是否处于空闲状态
        Long boatId = reqBody.getBoatId();
        SysBoat sysBoat = sysBoatService.selectSysBoatByBoatId(boatId);
        if (Objects.isNull(sysBoat) || sysBoat.getStatus() != BoatStatus.Idle.getCode()) {
            throw new ServiceException("当前所选船只已被占用，请更换船只重新下单!", 406);
        }

//        checkIfHaveUnfinishedOrderOnBoat(reqBody.getBoatId());

        //检查当前船只是否存在使用中的订单(如：1，扫码后还未开始计费的订单,2，已开始计费但还未结束计费的订单)，如果存在则不允许在当前船只上再创建新的订单
        String boatIdentityId = sysBoat.getBoatIdentityId();
        if (Objects.nonNull(boatIdentityId)) {
            UserBoatOrderInfo userBoatOrderInfo = userBoatOrderInfoService.getUserBoatOrderInfoOfUsingStatusByBoatIdentityId(boatIdentityId);
            if (Objects.nonNull(userBoatOrderInfo)) { //当前船只存在使用中的订单
                throw new ServiceException("创建订单失败，当前船只存在使用中的订单[订单ID:" + userBoatOrderInfo.getOrderId() + "]!", 500);
            }
        }

        //创建计时计费订单对象
        SysJishiOrder sysJishiOrder = new SysJishiOrder();
        BeanUtils.copyProperties(reqBody, sysJishiOrder);
        Map<String, Object> resultMap = null;
        try {
            resultMap = sysOrderService.createJishiOrder(sysJishiOrder);
        } catch (DuplicateKeyException e) {
            log.error("计时订单创建失败，用户短时间内多次重复创建订单！");
            return error("请勿重复创建订单！");
        } catch (Exception e) {
            return error("订单创建失败！");
        }

        if (Objects.isNull(resultMap) || resultMap.isEmpty()) {
            return error("订单创建失败");
        } else {
            return AjaxResult.success("订单创建成功", resultMap);
        }
    }

    /**
     * 创建套餐计费订单。
     *
     * @return
     */
    @PostMapping("/createTaocanOrder")
    public AjaxResult createTaocanOrder(@RequestBody CreateTaocanOrderReqBody reqBody) {
        log.info("createTaocanOrder reqBody: {}", reqBody);

        SysWharf sysWharf = sysWharfService.selectWharfByWharfId(reqBody.getWharfId());
        //检查确认用户下单时所在位置距离所选码头的直线距离是否在指定范围内
//        confirmUserDistance(reqBody.getLongitude(), reqBody.getLatitude(), sysWharf);

        //检查当前用户是否满足创建新订单的条件。
        confirmIfAllowedCreateNewOrder();

        //再次检查当前所选船只是否处于空闲状态
        Long boatId = reqBody.getBoatId();
        SysBoat sysBoat = sysBoatService.selectSysBoatByBoatId(boatId);
        if (Objects.isNull(sysBoat) || sysBoat.getStatus() != BoatStatus.Idle.getCode()) {
            throw new ServiceException("当前所选船只被占用，请更换船只重新下单!", 406);
        }

        //根据船只id检查指定的船只当前是否存在未完成的订单
//        checkIfHaveUnfinishedOrderOnBoat(reqBody.getBoatId());

        //检查当前船只是否存在使用中的订单(如：1，扫码后还未开始计费的订单,2，已开始计费但还未结束计费的订单)，如果存在则不允许在当前船只上再创建新的订单
        String boatIdentityId = sysBoat.getBoatIdentityId();
        if (Objects.nonNull(boatIdentityId)) {
            UserBoatOrderInfo userBoatOrderInfo = userBoatOrderInfoService.getUserBoatOrderInfoOfUsingStatusByBoatIdentityId(boatIdentityId);
            if (Objects.nonNull(userBoatOrderInfo)) { //当前船只存在使用中的订单
                throw new ServiceException("创建订单失败，当前船只存在使用中的订单[订单ID:" + userBoatOrderInfo.getOrderId() + "]!", 500);
            }
        }

        //创建套餐计费订单对象
        SysTaocanOrder sysTaocanOrder = new SysTaocanOrder();
        BeanUtils.copyProperties(reqBody, sysTaocanOrder);
        Map<String, Object> resultMap = null;
        try {
            resultMap = sysOrderService.createTaocanOrder(sysTaocanOrder);
        } catch (DuplicateKeyException e) {
            log.error("套餐订单创建失败，用户短时间内多次重复创建订单！");
            return error("请勿重复创建订单！");
        } catch (Exception e) {
            return error("订单创建失败！");
        }

        if (Objects.isNull(resultMap) || resultMap.isEmpty()) {
            return error("订单创建失败");
        } else {
            return AjaxResult.success("订单创建成功", resultMap);
        }
    }

    /**
     * 创建拼团订单。
     *
     * @return
     */
    @PostMapping("/createPintuanOrder")
    public AjaxResult createPintuanOrder(@RequestBody CreatePintuanOrderReqBody reqBody) {
        //检查当前用户是否满足创建新订单的条件。
        confirmIfAllowedCreateNewOrder();

        //根据船只id检查指定的船只当前是否存在未完成的订单
//        checkIfHaveUnfinishedOrderOnBoat(reqBody.getBoatId());

        Map<String, Object> resultMap = sysOrderService.createPintuanOrder(reqBody);

        if (Objects.isNull(resultMap) || resultMap.isEmpty()) {
            return error("订单创建失败");
        }

        return AjaxResult.success("订单创建成功", resultMap);
    }

    /**
     * 检查确认用户下单时所在位置距离所选码头的直线距离是否在指定范围内。
     *
     * @param longitude
     * @param latitude
     * @param sysWharf
     */
    private void confirmUserDistance(Double longitude, Double latitude, SysWharf sysWharf) {
        log.info("confirmUserDistance longitude: {}, latitude: {}", longitude, latitude);

        if (Objects.isNull(longitude) || longitude == 0) return;
        if (Objects.isNull(latitude) || latitude == 0) return;

        //将用户坐标(腾讯地图坐标为gcj02)转换为wgs84
        double[] transformedCoord = CoordinateTransformUtil.gcj02ToWgs84(longitude, latitude);

        //获取码头坐标(码头坐标无需转换，其默认即为wgs84)
        double wharfLon = sysWharf.getLon().doubleValue();
        double wharfLat = sysWharf.getLat().doubleValue();

        //计算用户当前所在位置与码头之间的直线距离
        double distance = CoordinateCommonUtil.calcDistanceBetweenTwoPoints(transformedCoord[0], transformedCoord[1], wharfLon, wharfLat);
        log.info("confirmUserDistance 计算距离结果distance: {}", distance);
        if (distance > 200) {
            throw new ServiceException("您当前位置距离码头较远，无法创建订单！", 402);
        }
    }

    /**
     * 取消订单。
     *
     * @return
     */
    @PostMapping("/cancelOrder")
    public AjaxResult cancelOrder(@RequestBody CancelOrderReqBody reqBody) {
        String orderId = reqBody.getOrderId();
        //订单类型。0：计时收费订单，1：套餐收费订单，2：拼团订单。
        int orderType = reqBody.getOrderType();

        LoginUser loginUser = getLoginUser();
        Long boatId = null; //当前欲取消的订单对应的船只id
        if (orderType == 0) { //计时计费订单
            SysJishiOrder sysJishiOrder = sysJishiOrderService.selectSysJishiOrderByOrderId(orderId);
            if (Objects.nonNull(sysJishiOrder)) {
                //订单状态。0：待付款，1：待使用，2：使用中，3: 已关闭，4：已完成
                int orderStatus = sysJishiOrder.getOrderStatus();
                boatId = sysJishiOrder.getBoatId();
                if (orderStatus == 0) { //待付款
                    //取消支付倒计时
                    CacheUtils.remove(CacheConstants.Order_Pay_Status_Redis_Key, orderId + ":" + orderType);
                } else if (orderStatus == 1) { //待使用
                    //取消上船倒计时
                    CacheUtils.remove(CacheConstants.Order_Confirm_On_Boat_Status_Redis_Key, orderId + ":" + orderType);

                    //主动给用户退还已支付的押金
                    WxRefundReqBody refundRequestBody = new WxRefundReqBody();
                    refundRequestBody.setOrderId(orderId);
                    refundRequestBody.setOrderType(orderType);
                    refundRequestBody.setRefundType(Refund_Type.Refund_Deposit.ordinal());
                    refundRequestBody.setReason("用户取消订单，平台退还已支付款项");
                    wxPayV3Service.refund(refundRequestBody);
                } else {
                    return AjaxResult.error("当前订单已完成或正在使用中，无法关闭.");
                }

                Date currTime = new Date();

                sysJishiOrder.setOrderStatus(OrderStatus.Closed.getCode());
                sysJishiOrder.setOrderStatusDesc(OrderStatus.Closed.getInfo());
                sysJishiOrder.setRemark("用户主动取消订单");
                sysJishiOrder.setClosedTime(currTime);
                sysJishiOrderService.updateSysJishiOrder(sysJishiOrder);

                //创建运营动态信息
                ScenicOperationDynamicInfo scenicOperationDynamicInfo = new ScenicOperationDynamicInfo();
                scenicOperationDynamicInfo.setOperationType(OperationType.Order_Closed);
                scenicOperationDynamicInfo.setScenicId(sysJishiOrder.getScenicId());
                scenicOperationDynamicInfo.setBoatId(sysJishiOrder.getBoatId());
                scenicOperationDynamicInfo.setBoatCode(sysJishiOrder.getBoatCode());
                scenicOperationDynamicInfo.setOrderId(orderId);
                scenicOperationDynamicInfo.setOrderType(orderType);
                scenicOperationDynamicInfo.setUserId(sysJishiOrder.getUserId());
                scenicOperationDynamicInfo.setCreateTime(currTime);
                IScenicOperationDynamicInfoService operationInfoService = SpringUtils.getBean(IScenicOperationDynamicInfoService.class);
                scenicOperationDynamicInfo.setContent(String.format("%s, %s船因用户主动取消订单，计时计费订单被关闭!", sdf.format(currTime), sysJishiOrder.getBoatCode()));
                operationInfoService.insertOperationInfo(scenicOperationDynamicInfo);

                //向景区监控中心发送运营动态信息
                WebSocketMessageHandler.sendMsgToScenicAdmin(sysJishiOrder.getScenicId(), scenicOperationDynamicInfo);
            } else {
                return AjaxResult.error("未找到当前订单id对应的订单.");
            }

        } else if (orderType == 1) {
            SysTaocanOrder sysTaocanOrder = sysTaocanOrderService.selectSysTaocanOrderByOrderId(orderId);
            if (Objects.nonNull(sysTaocanOrder)) {
                //订单状态。0：待付款，1：待使用，2：使用中，3: 已关闭，4：已完成
                int orderStatus = sysTaocanOrder.getOrderStatus();
                boatId = sysTaocanOrder.getBoatId();
                if (orderStatus == 0) {
                    //取消支付倒计时
                    CacheUtils.remove(CacheConstants.Order_Pay_Status_Redis_Key, orderId + ":" + orderType);
                } else if (orderStatus == 1) {
                    //取消上船倒计时
                    CacheUtils.remove(CacheConstants.Order_Confirm_On_Boat_Status_Redis_Key, orderId + ":" + orderType);

                    //主动给用户退还已支付的押金与套餐费
                    WxRefundReqBody refundRequestBody = new WxRefundReqBody();
                    refundRequestBody.setOrderId(orderId);
                    refundRequestBody.setOrderType(orderType);
                    refundRequestBody.setRefundType(Refund_Type.Refund_All.ordinal());
                    refundRequestBody.setReason("用户取消订单，平台退还已支付款项");
                    wxPayV3Service.refund(refundRequestBody);
                } else {
                    return AjaxResult.error("当前订单已完成或正在使用中，无法关闭.");
                }

                Date currTime = new Date();

                sysTaocanOrder.setOrderStatus(OrderStatus.Closed.getCode());
                sysTaocanOrder.setOrderStatusDesc(OrderStatus.Closed.getInfo());
                sysTaocanOrder.setRemark("用户主动取消订单");
                sysTaocanOrder.setClosedTime(currTime);
                sysTaocanOrderService.updateSysTaocanOrder(sysTaocanOrder);

                //创建运营动态信息
                ScenicOperationDynamicInfo scenicOperationDynamicInfo = new ScenicOperationDynamicInfo();
                scenicOperationDynamicInfo.setOperationType(OperationType.Order_Closed);
                scenicOperationDynamicInfo.setScenicId(sysTaocanOrder.getScenicId());
                scenicOperationDynamicInfo.setBoatId(sysTaocanOrder.getBoatId());
                scenicOperationDynamicInfo.setBoatCode(sysTaocanOrder.getBoatCode());
                scenicOperationDynamicInfo.setOrderId(orderId);
                scenicOperationDynamicInfo.setOrderType(orderType);
                scenicOperationDynamicInfo.setUserId(sysTaocanOrder.getUserId());
                scenicOperationDynamicInfo.setCreateTime(currTime);
                IScenicOperationDynamicInfoService operationInfoService = SpringUtils.getBean(IScenicOperationDynamicInfoService.class);
                scenicOperationDynamicInfo.setContent(String.format("%s, %s船因用户主动取消订单，套餐计费订单被关闭!", sdf.format(currTime), sysTaocanOrder.getBoatCode()));
                operationInfoService.insertOperationInfo(scenicOperationDynamicInfo);

                //向景区监控中心发送运营动态信息
                WebSocketMessageHandler.sendMsgToScenicAdmin(sysTaocanOrder.getScenicId(), scenicOperationDynamicInfo);
            } else {
                return AjaxResult.error("未找到当前订单id对应的订单.");
            }
        } else {
            return AjaxResult.error("订单类型参数orderType不符合要求");
        }

        if (Objects.nonNull(loginUser)) {
            //删除船只订单关联对象
            userBoatOrderInfoService.deleteUserBoatOrderInfoByUserId(loginUser.getUserId());
        }

        if (Objects.nonNull(boatId)) {
            //更新船只状态为"空闲"
            sysBoatService.updateBoatStatusByBoadId(boatId, BoatStatus.Idle);
        }

        return success();
    }

    /**
     * 根据船只id检查指定的船只当前是否存在未完成的订单。
     *
     * @param boatId
     */
    private void checkIfHaveUnfinishedOrderOnBoat(Long boatId) {
        UserBoatOrderInfo userBoatOrderInfo = userBoatOrderInfoService.selectBoatOrderInfoByBoatId(boatId);
        if (Objects.nonNull(userBoatOrderInfo)) {
            //只要boatOrderInfo不为null就表明当前船只存在未完成的订单
            throw new ServiceException(String.format("订单创建失败，当前船只存在未完成的订单[orderId=%s]。", userBoatOrderInfo.getOrderId()));
        }
    }

    /**
     * 根据订单id查询指定订单。
     *
     * @return
     */
    @GetMapping("/queryOrder/{orderId}")
    public AjaxResult queryOrder(@PathVariable("orderId") String orderId) {
        SysOrderDto orderDto = null;
        //先查询该订单是否是计时计费订单
        SysJishiOrder sysJishiOrder = sysJishiOrderService.selectSysJishiOrderByOrderId(orderId);
        if (Objects.nonNull(sysJishiOrder)) {
            orderDto = new SysOrderDto();
            BeanUtils.copyProperties(sysJishiOrder, orderDto);
        } else {
            SysTaocanOrder sysTaocanOrder = sysTaocanOrderService.selectSysTaocanOrderByOrderId(orderId);
            if (Objects.nonNull(sysTaocanOrder)) {
                orderDto = new SysOrderDto();
                BeanUtils.copyProperties(sysTaocanOrder, orderDto);
            } else {
                SysPintuanChildrenOrder sysPintuanChildrenOrder = sysPintuanChildrenOrderService.selectSysPintuanChildrenOrderByOrderId(orderId);
                if (Objects.nonNull(sysPintuanChildrenOrder)) {
                    orderDto = new SysOrderDto();
                    BeanUtils.copyProperties(sysPintuanChildrenOrder, orderDto);
                    if (sysPintuanChildrenOrder.getPintuanStatus() == PintuanStatus.GroupBooking.getCode()) { //当前拼团订单正处于拼团中
                        long expireTime = CacheUtils.getExpire(CacheConstants.Pintuan_Order_Redis_Key, String.valueOf(sysPintuanChildrenOrder.getBoatId()));
                        if (expireTime < 0) expireTime = 0;
                        orderDto.setPintuanRemainTime(expireTime);
                    }
                }
            }
        }

        if (Objects.nonNull(orderDto)) {
            //获取订单状态。0：待付款，1：待使用，2：使用中，3: 已关闭，4：已完成
            int orderStatus = orderDto.getOrderStatus();
            if (orderStatus == OrderStatus.Unpaid.getCode()) { //待付款状态
                //获取支付倒计时剩余时长
                long remainPayTime = CacheUtils.getExpire(CacheConstants.Order_Pay_Status_Redis_Key, orderDto.getOrderId() + ":" + orderDto.getOrderType());
                if (remainPayTime >= 0) {
                    orderDto.setRemainPayTime(remainPayTime);
                }
            } else if (orderStatus == OrderStatus.ToUse.getCode()) { //待使用状态
                //获取扫码确认上船倒计时剩余时长
                long confirmOnBoatRemainTime = CacheUtils.getExpire(CacheConstants.Order_Confirm_On_Boat_Status_Redis_Key, orderDto.getOrderId() + ":" + orderDto.getOrderType());
                if (confirmOnBoatRemainTime >= 0) {
                    orderDto.setConfirmOnBoatRemainTime(confirmOnBoatRemainTime);
                }
            }

            return success(orderDto);
        } else {
            return error("订单不存在.");
        }
    }

    /**
     * 查询当前用户名下的正在拼团中的订单。单个用户名下拼团中的订单通常只有一个。
     *
     * @return
     */
    @GetMapping("/queryGroupBookingOrder")
    public AjaxResult queryGroupBookingOrder() {
        LoginUser loginUser = getLoginUser();
        SysPintuanChildrenOrder groupBookingOrder = sysPintuanChildrenOrderService.queryGroupBookingOrderByUserId(loginUser.getUserId());

        //获取当前拼团倒计时剩余时长
        long expireTime = CacheUtils.getExpire(CacheConstants.Pintuan_Order_Redis_Key, String.valueOf(groupBookingOrder.getBoatId()));
        if (expireTime < 0) expireTime = 0;

        //设置当前拼团倒计时剩余时长
        groupBookingOrder.setPintuanRemainTime(expireTime);

        if (Objects.nonNull(groupBookingOrder)) {
            return success(Collections.singletonList(groupBookingOrder));
        } else {
            return success(Collections.emptyList());
        }
    }

    /**
     * 查询当前用户名下拼团成功的订单。
     *
     * @return
     */
    @GetMapping("/queryGroupBookingSucceedOrder")
    public AjaxResult queryGroupBookingSucceedOrder() {
        LoginUser loginUser = getLoginUser();
        List<SysPintuanChildrenOrder> groupBookingSucceedOrderList = sysPintuanChildrenOrderService.queryGroupBookingSucceedOrder(loginUser.getUserId());

        if (Objects.nonNull(groupBookingSucceedOrderList) && groupBookingSucceedOrderList.size() > 0) {
            return success(groupBookingSucceedOrderList);
        } else {
            return success(Collections.emptyList());
        }
    }

    /**
     * 查询当前用户名下拼团失败的订单。
     *
     * @return
     */
    @GetMapping("/queryGroupBookingFailedOrder")
    public AjaxResult queryGroupBookingFailedOrder() {
        LoginUser loginUser = getLoginUser();
        List<SysPintuanChildrenOrder> groupBookingFailedOrderList = sysPintuanChildrenOrderService.queryGroupBookingFailedOrder(loginUser.getUserId());

        if (Objects.nonNull(groupBookingFailedOrderList) && groupBookingFailedOrderList.size() > 0) {
            return success(groupBookingFailedOrderList);
        } else {
            return success(Collections.emptyList());
        }
    }

    /**
     * 根据拼团子订单id获取当前拼团状态。
     *
     * @return
     */
    @GetMapping("/getGroupBookingStatus/{orderId}")
    public AjaxResult getGroupBookingStatus(@PathVariable("orderId") String orderId) {
        AjaxResult ajaxResult = AjaxResult.success();
        ajaxResult.put("orderId", orderId);

        LoginUser loginUser = getLoginUser();

        SysPintuanChildrenOrder sysPintuanChildrenOrder = sysPintuanChildrenOrderService.selectSysPintuanChildrenOrderByOrderId(orderId);
        if (Objects.isNull(sysPintuanChildrenOrder)) {
            throw new ServiceException("未找到对应的拼团订单.");
        }

        if (!Objects.equals(sysPintuanChildrenOrder.getUserId(), loginUser.getUserId())) {
            throw new ServiceException("当前用户名下未找到此id的拼团订单.");
        }

//        if (sysPintuanChildrenOrder.getPintuanStatus() != PintuanStatus.GroupBooking.getCode()) {
//            throw new ServiceException("当前订单并未处于拼团中");
//        }

        //获取当前拼团倒计时剩余时长
        long expireTime = CacheUtils.getExpire(CacheConstants.Pintuan_Order_Redis_Key, String.valueOf(sysPintuanChildrenOrder.getBoatId()));
        if (expireTime < 0) expireTime = 0;
        ajaxResult.put("pintuanStatus", sysPintuanChildrenOrder.getPintuanStatus());
        ajaxResult.put("pintuanStatusDesc", sysPintuanChildrenOrder.getPintuanStatusDesc());
        ajaxResult.put("pintuanRemainTime", expireTime);

        SysPintuanParentOrder sysPintuanParentOrder = sysPintuanParentOrderService.selectSysPintuanParentOrderByOrderId(sysPintuanChildrenOrder.getParentOrderId());
        List<SysPintuanChildrenOrder> pintuanChildrenOrderList = sysPintuanParentOrder.getPintuanChildrenOrderList();
        List<Map<String, Object>> pintuanUserList = new ArrayList<>();
        if (Objects.nonNull(pintuanChildrenOrderList) && pintuanChildrenOrderList.size() > 0) {
            pintuanChildrenOrderList.forEach(childrenOrder -> {
                Map<String, Object> pintuanUserInfo = new HashMap<>();
                pintuanUserInfo.put("userId", childrenOrder.getUserId());
                pintuanUserInfo.put("userProfile", "");
                pintuanUserList.add(pintuanUserInfo);
            });
        }
        ajaxResult.put("pintuanUserList", pintuanUserList);

        return ajaxResult;
    }

    /**
     * 根据指定用户名下的所有订单。
     *
     * @return
     */
    @GetMapping("/queryAllOrder/{userId}")
    public AjaxResult queryAllOrder(@PathVariable("userId") Long userId) {
        List<SysOrderDto> orderDtos = new ArrayList<>();
        //先查询所有的计时计费订单
        List<SysJishiOrder> allSysJishiOrder = sysJishiOrderService.queryAllOrder(userId);
        if (Objects.nonNull(allSysJishiOrder) && allSysJishiOrder.size() > 0) {
            allSysJishiOrder.forEach(sysJishiOrder -> {
                SysOrderDto orderDto = new SysOrderDto();
                BeanUtils.copyProperties(sysJishiOrder, orderDto);
                orderDtos.add(orderDto);
            });
        }

        //再查询所有的套餐计费订单
        List<SysTaocanOrder> allTaocanOrder = sysTaocanOrderService.queryAllOrder(userId);
        if (Objects.nonNull(allTaocanOrder) && allTaocanOrder.size() > 0) {
            allTaocanOrder.forEach(taocanOrder -> {
                SysOrderDto orderDto = new SysOrderDto();
                BeanUtils.copyProperties(taocanOrder, orderDto);
                orderDtos.add(orderDto);
            });
        }

        if (orderDtos.size() > 0) {
            //再对所有订单按照创建时间逆序排序
            Collections.sort(orderDtos, (o1, o2) -> {
                if (o1.getCreateTime().getTime() >= o2.getCreateTime().getTime()) {
                    return -1;
                } else {
                    return 1;
                }
            });

            //获取第一个订单的状态。
            SysOrderDto firstOrder = orderDtos.get(0);
            //查询第一个订单当前的订单状态。订单状态。0：待付款，1：待使用，2：使用中，3: 已关闭，4：已完成
            int orderStatus = firstOrder.getOrderStatus();
            if (orderStatus == OrderStatus.Unpaid.getCode()) { //待付款
                //获取支付倒计时剩余时长
                long remainPayTime = CacheUtils.getExpire(CacheConstants.Order_Pay_Status_Redis_Key, firstOrder.getOrderId() + ":" + firstOrder.getOrderType());
                if (remainPayTime >= 0) {
                    firstOrder.setRemainPayTime(remainPayTime);
                }
            } else if (orderStatus == OrderStatus.ToUse.getCode()) { //待使用
                //获取扫码确认上船倒计时剩余时长
                long confirmOnBoatRemainTime = CacheUtils.getExpire(CacheConstants.Order_Confirm_On_Boat_Status_Redis_Key, firstOrder.getOrderId() + ":" + firstOrder.getOrderType());
                if (confirmOnBoatRemainTime >= 0) {
                    firstOrder.setConfirmOnBoatRemainTime(confirmOnBoatRemainTime);
                }
            }

        }

        return success(orderDtos);
    }

    /**
     * 查询某用户所有待付款订单。
     *
     * @return
     */
    @GetMapping("/queryUnpaidOrder")
    public AjaxResult queryUnpaidOrder(@RequestParam("userId") Long userId) {
        SysOrderDto unpaidOrder = null;
        //先查询是否存在待支付的计时计费订单
        SysJishiOrder sysJishiOrder = sysJishiOrderService.queryUnpaidOrder(userId);
        if (Objects.nonNull(sysJishiOrder)) {
            unpaidOrder = new SysOrderDto();
            BeanUtils.copyProperties(sysJishiOrder, unpaidOrder);
        } else {
            //TODO 再查询是否存在待支付的其他类型订单
            SysTaocanOrder sysTaocanOrder = sysTaocanOrderService.queryUnpaidOrder(userId);
            if (Objects.nonNull(sysTaocanOrder)) {
                unpaidOrder = new SysOrderDto();
                BeanUtils.copyProperties(sysTaocanOrder, unpaidOrder);
            }
        }

        if (Objects.nonNull(unpaidOrder)) {
            //获取支付倒计时剩余时长
            long remainPayTime = CacheUtils.getExpire(CacheConstants.Order_Pay_Status_Redis_Key, unpaidOrder.getOrderId() + ":" + unpaidOrder.getOrderType());
            if (remainPayTime >= 0) {
                unpaidOrder.setRemainPayTime(remainPayTime);
            }
            return success(Collections.singletonList(unpaidOrder));
        } else {
            return success(Collections.emptyList());
        }
    }

    /**
     * 查询某用户所有待使用订单。
     *
     * @return
     */
    @GetMapping("/queryToUseOrder")
    public AjaxResult queryToUseOrder(@RequestParam("userId") Long userId) {
        SysOrderDto toUseOrder = null;
        //先查询是否存在待使用的计时计费订单
        SysJishiOrder sysJishiOrder = sysJishiOrderService.queryToUseOrder(userId);
        if (Objects.nonNull(sysJishiOrder)) {
            toUseOrder = new SysOrderDto();
            BeanUtils.copyProperties(sysJishiOrder, toUseOrder);
        } else {
            //TODO 再查询是否存在待使用的其他类型订单
            SysTaocanOrder sysTaocanOrder = sysTaocanOrderService.queryToUseOrder(userId);
            if (Objects.nonNull(sysTaocanOrder)) {
                toUseOrder = new SysOrderDto();
                BeanUtils.copyProperties(sysTaocanOrder, toUseOrder);
            }
        }

        if (Objects.nonNull(toUseOrder)) {
            //获取扫码确认上船倒计时剩余时长
            long confirmOnBoatRemainTime = CacheUtils.getExpire(CacheConstants.Order_Confirm_On_Boat_Status_Redis_Key, toUseOrder.getOrderId() + ":" + toUseOrder.getOrderType());
            if (confirmOnBoatRemainTime >= 0) {
                toUseOrder.setConfirmOnBoatRemainTime(confirmOnBoatRemainTime);
            }
            return success(Collections.singletonList(toUseOrder));
        } else {
            return success(Collections.emptyList());
        }
    }

    /**
     * 查询某用户所有正在使用中的订单。
     *
     * @return
     */
    @GetMapping("/queryUsingOrder")
    public AjaxResult queryUsingOrder(@RequestParam("userId") Long userId) {
        SysOrderDto usingOrder = null;
        //先查询是否存在正在使用中的计时计费订单
        SysJishiOrder sysJishiOrder = sysJishiOrderService.queryUsingOrder(userId);
        if (Objects.nonNull(sysJishiOrder)) {
            usingOrder = new SysOrderDto();
            BeanUtils.copyProperties(sysJishiOrder, usingOrder);
        } else {
            //TODO 再查询是否存在使用中的其他类型订单
            SysTaocanOrder sysTaocanOrder = sysTaocanOrderService.queryUsingOrder(userId);
            if (Objects.nonNull(sysTaocanOrder)) {
                //超时时长(单位：分钟)
                int overtime = 0;
                Date beginTime = sysTaocanOrder.getBeginTime();
                //注意beginTime可能会为空，此处需要进行判空
                if (Objects.nonNull(beginTime)) {
                    overtime = (int) (System.currentTimeMillis() - beginTime.getTime()) / (60 * 1000) - sysTaocanOrder.getTaocanDuration();
                }
                double overtimeFee = sysTaocanOrder.getJishiPrice() * overtime; //计时计费计价单位默认为：元/分钟
                sysTaocanOrder.setOvertime(overtime);
                sysTaocanOrder.setOvertimeFee(overtimeFee);

                usingOrder = new SysOrderDto();
                BeanUtils.copyProperties(sysTaocanOrder, usingOrder);
            }
        }

        if (Objects.nonNull(usingOrder)) {
            return success(Collections.singletonList(usingOrder));
        } else {
            return success(Collections.emptyList());
        }
    }

    /**
     * 查询当前进行中(待支付/待使用/使用中)的订单。
     *
     * @return
     */
    @GetMapping("/queryInProgressOrder")
    public AjaxResult queryInProgressOrder(@RequestParam("userId") Long userId) {
        SysOrderDto inProgressOrder = null;
        //先查询是否存在进行中的计时计费订单
        SysJishiOrder sysJishiOrder = sysJishiOrderService.queryInProgressOrder(userId);
        if (Objects.nonNull(sysJishiOrder)) {
            inProgressOrder = new SysOrderDto();
            BeanUtils.copyProperties(sysJishiOrder, inProgressOrder);
        } else {
            SysTaocanOrder sysTaocanOrder = sysTaocanOrderService.queryInProgressOrder(userId);
            if (Objects.nonNull(sysTaocanOrder)) {
                //超时时长(单位：分钟)
                int overtime = 0;
                double overtimeFee = 0;
                Date beginTime = sysTaocanOrder.getBeginTime();
                //注意beginTime可能会为空，此处需要进行判空
                if (Objects.nonNull(beginTime)) {
                    overtime = (int) (System.currentTimeMillis() - beginTime.getTime()) / (60 * 1000) - sysTaocanOrder.getTaocanDuration();
                }
                if (overtime < 0) {
                    overtime = 0;
                }
                overtimeFee = sysTaocanOrder.getJishiPrice() * overtime; //计时计费计价方式：price 元/分钟

                sysTaocanOrder.setOvertime(overtime);
                sysTaocanOrder.setOvertimeFee(overtimeFee);

                inProgressOrder = new SysOrderDto();
                BeanUtils.copyProperties(sysTaocanOrder, inProgressOrder);
            }
        }

        if (Objects.nonNull(inProgressOrder)) {
            //获取订单状态。0：待付款，1：待使用，2：使用中，3: 已关闭，4：已完成
            int orderStatus = inProgressOrder.getOrderStatus();
            if (orderStatus == OrderStatus.Unpaid.getCode()) { //待付款状态
                //获取支付倒计时剩余时长
                long remainPayTime = CacheUtils.getExpire(CacheConstants.Order_Pay_Status_Redis_Key, inProgressOrder.getOrderId() + ":" + inProgressOrder.getOrderType());
                if (remainPayTime >= 0) {
                    inProgressOrder.setRemainPayTime(remainPayTime);
                }
            } else if (orderStatus == OrderStatus.ToUse.getCode()) { //待使用状态
                //获取扫码确认上船倒计时剩余时长
                long confirmOnBoatRemainTime = CacheUtils.getExpire(CacheConstants.Order_Confirm_On_Boat_Status_Redis_Key, inProgressOrder.getOrderId() + ":" + inProgressOrder.getOrderType());
                if (confirmOnBoatRemainTime >= 0) {
                    inProgressOrder.setConfirmOnBoatRemainTime(confirmOnBoatRemainTime);
                }
            }

            return success(Collections.singletonList(inProgressOrder));
        } else {
            return success(Collections.emptyList());
        }
    }

    /**
     * 查询某用户所有已完成(含已关闭)的订单。
     *
     * @return
     */
    @GetMapping("/queryFulfilledOrder")
    public AjaxResult queryFulfilledOrder(@RequestParam("userId") Long userId) {
        List<SysOrderDto> orderDtos = new ArrayList<>();
        //先查询所有已完成(含已关闭)的计时计费订单
        List<SysJishiOrder> sysJishiOrders = sysJishiOrderService.queryFulfilledOrder(userId);
        if (Objects.nonNull(sysJishiOrders) && sysJishiOrders.size() > 0) {
            sysJishiOrders.forEach(sysJishiOrder -> {
                SysOrderDto orderDto = new SysOrderDto();
                BeanUtils.copyProperties(sysJishiOrder, orderDto);
                orderDtos.add(orderDto);
            });
        }

        //再查询所有已完成(含已关闭)的套餐计费订单
        List<SysTaocanOrder> sysTaocanOrders = sysTaocanOrderService.queryFulfilledOrder(userId);
        if (Objects.nonNull(sysTaocanOrders) && sysTaocanOrders.size() > 0) {
            sysTaocanOrders.forEach(sysTaocanOrder -> {
                SysOrderDto orderDto = new SysOrderDto();
                BeanUtils.copyProperties(sysTaocanOrder, orderDto);
                orderDtos.add(orderDto);
            });
        }

        //将所有订单按照创建时间倒序排列
        Collections.sort(orderDtos, (o1, o2) -> {
            if (o1.getCreateTime().getTime() >= o2.getCreateTime().getTime()) {
                return -1;
            } else {
                return 1;
            }
        });

        return success(orderDtos);
    }


    /**
     * 查询订单支付状态。该接口提供给外部调用，其查询支付状态实际是去数据库中查询，而非通过微信支付官方接口查询。
     *
     * @param outOrderNo
     * @return
     */
    @GetMapping("/queryPayStatus/{outOrderNo}")
    public AjaxResult queryPayStatus(@PathVariable("outOrderNo") String outOrderNo) {
        Map<String, Object> resultMap = sysOrderService.queryPayStatus(outOrderNo);
        return success(resultMap);
    }

    /**
     * 查询订单退款状态。
     *
     * @param outRefundNo
     * @param refundType  退款类型。0：只退押金，1：只退订单费用，2：退押金+订单费用。
     * @return
     */
    @GetMapping("/queryRefundStatus/{outRefundNo}/{refundType}")
    public AjaxResult queryRefundStatus(@PathVariable("outRefundNo") String outRefundNo, @PathVariable("refundType") int refundType) {
        Map<String, Object> resultMap = sysOrderService.queryRefundStatus(outRefundNo, refundType);
        return success(resultMap);
    }

    /**
     * @param reqBody
     * @return
     */
    @PostMapping("/confirmBoat")
    public AjaxResult confirmBoat(@RequestBody ConfirmBoatReqBody reqBody) {
        AjaxResult ajaxResult = new AjaxResult();
        Map<String, Object> resultMap = new HashMap<>();

        long scannedScenicId = reqBody.getScenicId();
        String scannedBoatCode = reqBody.getBoatCode();
        resultMap.put("scannedScenicId", scannedScenicId);
        resultMap.put("scannedBoatCode", scannedBoatCode);

        //获取当前登录用户
        LoginUser loginUser = getLoginUser();
        Long userId = loginUser.getUserId();

        //首先判断当前订单对应的船只此时是否处于"上船倒计时中"状态，如果不是则扫码确认失败。比如离线等状态时就不允许扫码确认上船。
        UserBoatOrderInfo userBoatOrderInfo = userBoatOrderInfoService.selectUserBoatOrderInfoByUserId(userId);
        if (Objects.nonNull(userBoatOrderInfo)) {
            Long boatId = userBoatOrderInfo.getBoatId();
            SysBoat sysBoat = sysBoatService.selectSysBoatByBoatId(boatId);
            if (Objects.nonNull(sysBoat)) {
                int boatStatus = sysBoat.getStatus();
                if (boatStatus != BoatStatus.OnBoatCounting.getCode() && boatStatus != BoatStatus.Idle.getCode()) {
                    //如果船只当前状态不为"上船倒计时中"或"空闲"状态则不允许扫码确认
                    ajaxResult.setCode(400).setMsg("匹配失败,当前订单对应的船只状态异常！").put("match", false);
                    return ajaxResult;
                }
            } else {
                ajaxResult.setCode(400).setMsg("匹配失败,当前订单对应的船只未找到！").put("match", false);
                return ajaxResult;
            }
        } else {
            ajaxResult.setCode(400).setMsg("匹配失败,用户，订单及船只信息不匹配！").put("match", false);
            return ajaxResult;
        }

        //用户当前待使用的订单
        BaseOrder toUserOrder = null;
        //先检查用户名下是否存在待使用的计时计费订单
        SysJishiOrder sysJishiOrder = sysJishiOrderService.queryToUseOrder(userId);
        if (Objects.nonNull(sysJishiOrder)) {
            toUserOrder = sysJishiOrder;
        } else {
            //再检查用户名下是否存在待使用的套餐计费订单
            SysTaocanOrder sysTaocanOrder = sysTaocanOrderService.queryToUseOrder(userId);
            if (Objects.nonNull(sysTaocanOrder)) {
                toUserOrder = sysTaocanOrder;
            } else {
                //再检查用户名下是否存在待使用的拼团订单
                SysPintuanChildrenOrder sysPintuanChildrenOrder = sysPintuanChildrenOrderService.queryToUseOrder(userId);
                if (Objects.nonNull(sysPintuanChildrenOrder)) {
                    toUserOrder = sysPintuanChildrenOrder;
                }
            }
        }

        if (Objects.nonNull(toUserOrder)) {
            long scenicIdOnOrder = toUserOrder.getScenicId();
            String boatCodeOnOrder = toUserOrder.getBoatCode();
            resultMap.put("scenicIdOnOrder", scenicIdOnOrder);
            resultMap.put("boatCodeOnOrder", boatCodeOnOrder);
            //先判断景区是否一致
//            if (scenicIdOnOrder == scannedScenicId) {
            //再判断船只编号是否一致
            if (boatCodeOnOrder.equals(scannedBoatCode)) { //用户扫码确认上船成功
                log.info("用户扫码确认上船成功!");
                //用户扫码确认上船成功
                ajaxResult.setCode(200).setMsg("匹配成功").put("match", true);

                int orderType = toUserOrder.getOrderType();
                if (orderType == 0) { //计时计费订单
                    //删除redis中与用户扫码确认上船状态相关的key，停止扫码确认上船倒计时。
                    CacheUtils.remove(CacheConstants.Order_Confirm_On_Boat_Status_Redis_Key, toUserOrder.getOrderId() + ":" + toUserOrder.getOrderType());

                    SysJishiOrder jishiOrder = (SysJishiOrder) toUserOrder;
                    //更新订单状态为使用中
                    jishiOrder.setOrderStatus(OrderStatus.Using.getCode());
                    jishiOrder.setOrderStatusDesc(OrderStatus.Using.getInfo());
                    sysJishiOrderService.updateSysJishiOrder(sysJishiOrder);

                    //更新船只与订单信息关联表中订单的状态为"使用中"
                    userBoatOrderInfo.setOrderStatus(OrderStatus.Using.getCode());
                    userBoatOrderInfo.setOrderStatusDesc(OrderStatus.Using.getInfo());
                    userBoatOrderInfoService.updateBoatOrderInfo(userBoatOrderInfo);

                    //发送消息给当前订单对应的船端软件，告知用户已支付成功，且扫码确认上船成功，船锁可以开启了。
                    Long boatId = jishiOrder.getBoatId();
                    String boatCode = jishiOrder.getBoatCode();
                    Channel socketChannel = SocketChannelManager.getSocketChannelByBoatId(boatId);
                    if (Objects.nonNull(socketChannel)) {
                        SocketChannelManager.sendPayStatusInfoMessage(socketChannel, new PayStatusInfo((byte) 1));
                    } else {
                        log.error("船只[boatId={},boatCode={}]与后端未连接，后端向当前船端发送用户已扫码上船的指令失败！", boatId, boatCode);
                    }

                    //更新船只状态为“开锁等待中”
                    sysBoatService.updateBoatStatusByBoadId(boatId, BoatStatus.UnlockWaiting);

                    if (EnvConfig.isDev()) {
                        //模拟船锁解锁以及船只航行。
//                            mockUnlockAndUsing(userId, 10000L, 20000L);
                    }
                } else if (orderType == 1) { //套餐订单
                    //删除redis中与用户扫码确认上船状态相关的key，停止扫码确认上船倒计时。
                    CacheUtils.remove(CacheConstants.Order_Confirm_On_Boat_Status_Redis_Key, toUserOrder.getOrderId() + ":" + toUserOrder.getOrderType());

                    SysTaocanOrder taocanOrder = (SysTaocanOrder) toUserOrder;
                    //更新订单状态为使用中
                    taocanOrder.setOrderStatus(OrderStatus.Using.getCode());
                    taocanOrder.setOrderStatusDesc(OrderStatus.Using.getInfo());
                    sysTaocanOrderService.updateSysTaocanOrder(taocanOrder);

                    //更新船只与订单信息关联表中订单的状态为"使用中"
                    userBoatOrderInfo.setOrderStatus(OrderStatus.Using.getCode());
                    userBoatOrderInfo.setOrderStatusDesc(OrderStatus.Using.getInfo());
                    userBoatOrderInfoService.updateBoatOrderInfo(userBoatOrderInfo);

                    //发送消息给当前订单对应的船端软件，告知用户已支付成功，且扫码确认上船成功，船锁可以开启了。
                    Long boatId = taocanOrder.getBoatId();
                    String boatCode = taocanOrder.getBoatCode();
                    Channel socketChannel = SocketChannelManager.getSocketChannelByBoatId(boatId);
                    if (Objects.nonNull(socketChannel)) {
                        SocketChannelManager.sendPayStatusInfoMessage(socketChannel, new PayStatusInfo((byte) 1));
                    } else {
                        log.error("船只[boatId={},boatCode={}]与后端的连接为空，后端向当前船端发送用户已扫码上船的指令失败！", boatId, boatCode);
                    }

                    //更新船只状态为“开锁等待中”
                    sysBoatService.updateBoatStatusByBoadId(boatId, BoatStatus.UnlockWaiting);

                    if (EnvConfig.isDev()) {
                        //模拟船锁解锁以及船只航行。
//                            mockUnlockAndUsing(userId, 10000L, 20000L);
                    }
                } else if (orderType == 2) { //拼团订单
                    SysPintuanChildrenOrder sysPintuanChildrenOrder = (SysPintuanChildrenOrder) toUserOrder;
                    //先更新当前子订单的状态
                    sysPintuanChildrenOrder.setOnBoatStatus(1); //将当前子订单的扫码上船状态更新为已上船。
                    sysPintuanChildrenOrderService.updateSysPintuanChildrenOrder(sysPintuanChildrenOrder);

                    //再检查当前拼团订单中所有参与拼团的订单是否都已完成扫码上船
                    SysPintuanParentOrder sysPintuanParentOrder = sysPintuanParentOrderService.selectSysPintuanParentOrderByOrderId(sysPintuanChildrenOrder.getParentOrderId());
                    List<SysPintuanChildrenOrder> pintuanChildrenOrderList = sysPintuanParentOrder.getPintuanChildrenOrderList();

                    boolean notAllConfirmOnBoat = false;
                    for (SysPintuanChildrenOrder pintuanChildrenOrder : pintuanChildrenOrderList) {
                        //只要有一个订单未完成扫码上船即认为未全部完成扫码上船
                        if (pintuanChildrenOrder.getOnBoatStatus() != 1) {
                            notAllConfirmOnBoat = true;
                        }
                    }

                    if (notAllConfirmOnBoat) { //未全部完成扫码上船
                        log.info("还有拼团用户未完成扫码上船，继续等待...");
                    } else { //已全部完成扫码上船
                        log.info("所有拼团用户都已完成扫码上船!");

                        //停止扫码确认上船倒计时
                        CacheUtils.remove(CacheConstants.Order_Confirm_On_Boat_Status_Redis_Key, sysPintuanParentOrder.getOrderId() + ":" + sysPintuanParentOrder.getOrderType());

                        //发送消息给当前订单对应的船端软件，告知当前拼团订单中的所有用户均已支付成功，且均已扫码确认上船成功，船锁可以开启了。
                        Long boatId = sysPintuanParentOrder.getBoatId();
                        Channel socketChannel = SocketChannelManager.getSocketChannelByBoatId(boatId);
                        if (Objects.nonNull(socketChannel)) {
                            SocketChannelManager.sendPayStatusInfoMessage(socketChannel, new PayStatusInfo((byte) 1));
                        }

                        //将船只状态从"上船倒计时中"更新为“开锁等待中”
                        SysBoat sysBoat = new SysBoat();
                        sysBoat.setBoatId(sysPintuanParentOrder.getBoatId());
                        sysBoat.setStatus(BoatStatus.UnlockWaiting.getCode());
                        sysBoat.setStatusDesc(BoatStatus.UnlockWaiting.getInfo());
                        sysBoatService.updateSysBoat(sysBoat);

                        //模拟开锁及行驶
//                            sysOrderService.mockUnlockAndUsingForPintuanOrder(sysPintuanParentOrder, 10000L, 20000L);
                    }
                }
            } else {
                ajaxResult.setCode(402).setMsg("匹配失败,扫码的船只编号与待使用订单中的船只编号不一致").put("match", false);
            }
//            } else {
//                ajaxResult.setCode(401).setMsg("匹配失败,扫码的船只所属景区id与待使用订单中的船只所属景区id不一致").put("match", false);
//            }
        } else {
            resultMap.put("scenicIdOnOrder", 0);
            resultMap.put("boatCodeOnOrder", "");

            ajaxResult.setCode(400).setMsg("匹配失败,当前用户没有待使用的订单").put("match", false);
        }

        ajaxResult.setData(resultMap);
        return ajaxResult;
    }

    /**
     * 模拟船锁解锁以及船只航行。
     */
    public void mockUnlockAndUsing(Long userId, Long unlockDelayTime, Long mockUsingTime) {
        log.info("mockUnlockAndUsing unlockDelayTime: {}, mockUsingTime: {}", unlockDelayTime, mockUsingTime);

        //模拟10秒后接收到船锁已解锁的通知
        new Timer().schedule(new TimerTask() {
            @Override
            public void run() {
                log.info("模拟开锁成功！订单开始计费.");
                //订单开始计费的时间
                Date beginTime = new Date();
                //先检查用户名下是否存在使用中的计时计费订单(确认上船后的订单状态已更新为使用中)
                SysJishiOrder usingJishiOrder = sysJishiOrderService.queryUsingOrder(userId);
                if (Objects.nonNull(usingJishiOrder)) {
                    //此时船锁已开启(由用户滑动解锁)，服务端也已接收到船锁已开启的信号，现在将订单状态更新为使用中

                    //将订单状态更新为“使用中”
                    usingJishiOrder.setOrderStatus(OrderStatus.Using.getCode());
                    usingJishiOrder.setOrderStatusDesc(OrderStatus.Using.getInfo());
                    //设置订单开始计费的时间
                    usingJishiOrder.setBeginTime(beginTime);
                    sysJishiOrderService.updateSysJishiOrder(usingJishiOrder);

                    //将船只状态从"上船倒计时中"更新为“手动驾驶中”
                    SysBoat sysBoat = new SysBoat();
                    sysBoat.setBoatId(usingJishiOrder.getBoatId());
                    sysBoat.setStatus(BoatStatus.ManualDriving.getCode());
                    sysBoat.setStatusDesc(BoatStatus.ManualDriving.getInfo());
                    sysBoatService.updateSysBoat(sysBoat);

                    //通知小程序客户端订单开始计费
                    OrderBeginChargeMsg jishiOrderBeginChargeMsg = new OrderBeginChargeMsg();
                    jishiOrderBeginChargeMsg.setOrderId(usingJishiOrder.getOrderId());
                    jishiOrderBeginChargeMsg.setOrderType(usingJishiOrder.getOrderType());
                    jishiOrderBeginChargeMsg.setBeginTime(beginTime);
                    Channel miniprogramWebsocketChannel = WebSocketChannelManager.getMiniprogramWebsocketChannelByUserId(usingJishiOrder.getUserId());
                    if (Objects.nonNull(miniprogramWebsocketChannel)) {
                        WebSocketMessageHandler.notifyMiniClientOrderBeginCharging(miniprogramWebsocketChannel, jishiOrderBeginChargeMsg);
                    } else {
                        log.error("当前下单用户[userId={}]与后端未连接，向该用户发送订单开始计费指令失败！", usingJishiOrder.getUserId());
                    }
                } else {
                    //再检查用户名下是否存在使用中的套餐计费订单
                    SysTaocanOrder usingTaocanOrder = sysTaocanOrderService.queryUsingOrder(userId);
                    if (Objects.nonNull(usingTaocanOrder)) {
                        //此时船锁已开启(由用户滑动解锁)，服务端也已接收到船锁已开启的信号，现在将订单状态更新为使用中

                        //将订单状态更新为“使用中”
                        usingTaocanOrder.setOrderStatus(OrderStatus.Using.getCode());
                        usingTaocanOrder.setOrderStatusDesc(OrderStatus.Using.getInfo());
                        //设置订单开始计费的时间
                        usingTaocanOrder.setBeginTime(beginTime);
                        sysTaocanOrderService.updateSysTaocanOrder(usingTaocanOrder);

                        //将船只状态从"上船倒计时中"更新为“手动驾驶中”
                        SysBoat sysBoat = new SysBoat();
                        sysBoat.setBoatId(usingTaocanOrder.getBoatId());
                        sysBoat.setStatus(BoatStatus.ManualDriving.getCode());
                        sysBoat.setStatusDesc(BoatStatus.ManualDriving.getInfo());
                        sysBoatService.updateSysBoat(sysBoat);

                        //通知小程序客户端订单开始计费
                        OrderBeginChargeMsg jishiOrderBeginChargeMsg = new OrderBeginChargeMsg();
                        jishiOrderBeginChargeMsg.setOrderId(usingTaocanOrder.getOrderId());
                        jishiOrderBeginChargeMsg.setOrderType(usingTaocanOrder.getOrderType());
                        jishiOrderBeginChargeMsg.setBeginTime(beginTime);
                        Channel miniprogramWebsocketChannel = WebSocketChannelManager.getMiniprogramWebsocketChannelByUserId(usingTaocanOrder.getUserId());
                        if (Objects.nonNull(miniprogramWebsocketChannel)) {
                            WebSocketMessageHandler.notifyMiniClientOrderBeginCharging(miniprogramWebsocketChannel, jishiOrderBeginChargeMsg);
                        } else {
                            log.error("当前下单用户[userId={}]与后端未连接，向该用户发送订单开始计费指令失败！", usingTaocanOrder.getUserId());
                        }
                    } else {
                        //再检查用户名下是否存在待使用的拼团订单
                    }
                }

                //模拟25秒后船锁关闭，订单停止计费
                new Timer().schedule(new TimerTask() {
                    @Override
                    public void run() {
                        log.info("模拟关锁成功！订单结束计费.");
                        //订单结束计费的时间
                        Date endTime = new Date();
                        //获取订单实际持续的收费时长，单位：毫秒。
                        long realDuration = endTime.getTime() - beginTime.getTime();

                        //将船只状态从"手动驾驶中"更新为“空闲”
                        SysBoat sysBoat = new SysBoat();
                        sysBoat.setStatus(BoatStatus.Idle.getCode());
                        sysBoat.setStatusDesc(BoatStatus.Idle.getInfo());

                        //先检查用户名下是否存在正在使用中的计时计费订单
                        SysJishiOrder usingJishiOrder = sysJishiOrderService.queryUsingOrder(userId);
                        if (Objects.nonNull(usingJishiOrder)) {
                            sysBoat.setBoatId(usingJishiOrder.getBoatId());

                            //将订单状态更新为“待付款”。注意：此处订单状态不能修改为“已完成”，因为此时订单费用还未支付，当前只是结束计费了，订单并未完成。
                            usingJishiOrder.setOrderStatus(OrderStatus.Unpaid.getCode());
                            usingJishiOrder.setOrderStatusDesc(OrderStatus.Unpaid.getInfo());

                            //设置订单停止计费的时间
                            usingJishiOrder.setEndTime(endTime);
//                                        usingJishiOrder.setClosedTime(endTime); //这里不能设置订单关闭时间，因为此时订单还未支付费用。

                            //计算计时计费订单的实际费用。
                            double orderFee = calculateJishiOrderFee(usingJishiOrder.getJishiId(), realDuration);

                            //设置订单实际持续的收费时长
                            usingJishiOrder.setRealDuration(realDuration);
                            //设置订单费用金额
                            usingJishiOrder.setOrderFee(orderFee);
                            sysJishiOrderService.updateSysJishiOrder(usingJishiOrder);

                            //通知小程序客户端结束当前计时计费订单的计费。
                            JishiOrderEndChargeMsg jishiOrderEndChargeMsg = new JishiOrderEndChargeMsg();
                            jishiOrderEndChargeMsg.setOrderId(usingJishiOrder.getOrderId());
                            jishiOrderEndChargeMsg.setBeginTime(usingJishiOrder.getBeginTime());
                            jishiOrderEndChargeMsg.setEndTime(usingJishiOrder.getEndTime());
                            jishiOrderEndChargeMsg.setRealDuration(usingJishiOrder.getRealDuration());
                            jishiOrderEndChargeMsg.setOrderFee(orderFee);

                            Channel miniprogramWebsocketChannel = WebSocketChannelManager.getMiniprogramWebsocketChannelByUserId(usingJishiOrder.getUserId());
                            if (Objects.nonNull(miniprogramWebsocketChannel)) {
                                WebSocketMessageHandler.notifyClientSideJishiOrderStopCharging(miniprogramWebsocketChannel, jishiOrderEndChargeMsg);
                            } else {
                                log.error("当前下单用户[userId={}]与后端未连接，向该用户发送订单结束计费指令失败！", usingJishiOrder.getUserId());
                            }
                        } else {
                            //检查用户名下是否存在正在使用中的套餐计费订单
                            SysTaocanOrder usingTaocanOrder = sysTaocanOrderService.queryUsingOrder(userId);
                            if (Objects.nonNull(usingTaocanOrder)) {
                                sysBoat.setBoatId(usingTaocanOrder.getBoatId());

                                //套餐计费订单此时需要先判断实际计费时长是否查过当前套餐包含的时长，如果没有超则此时可以直接结束并关闭订单。如果超过了
                                // 套餐时长则需要计算超过的时长，然后依据当前景区计时计费的规则来计算超出时长应补交的费用。
                                //将实际使用时长realDuration(单位：毫秒)换算成分钟(且取整)
                                int realMinute = (int) (realDuration / (60 * 1000));
                                //设置订单停止计费的时间
                                usingTaocanOrder.setEndTime(endTime);
                                //设置订单实际持续的收费时长
                                usingTaocanOrder.setRealDuration(realDuration);

                                //计算超时时长(单位：分钟)
                                int overtime = realMinute - usingTaocanOrder.getTaocanDuration();
                                if (overtime <= 0) {
                                    overtime = 0;
                                }
                                usingTaocanOrder.setOvertime(overtime);

                                //计算超时费用(单位：元)
                                double overtimeFee = 0;

                                TaocanOrderEndChargeMsg taocanOrderEndChargeMsg = new TaocanOrderEndChargeMsg();
                                taocanOrderEndChargeMsg.setOrderId(usingTaocanOrder.getOrderId());
                                taocanOrderEndChargeMsg.setBeginTime(beginTime);
                                taocanOrderEndChargeMsg.setEndTime(endTime);
                                taocanOrderEndChargeMsg.setRealDuration(realDuration);
                                taocanOrderEndChargeMsg.setTaocanDuration(usingTaocanOrder.getTaocanDuration());
                                taocanOrderEndChargeMsg.setOvertime(overtime);

                                if (realMinute > 0) { //订单总计费时长超过1分钟(含)
                                    if (overtime > 0) { //套餐订单超时，且超过1分钟(含)以上
                                        //将订单状态更新为“待付款”。注意：此时订单状态不能修改为“已完成”，因为此时订单超时费用还未支付，当前只是结束计费了，订单并未完成。
                                        usingTaocanOrder.setOrderStatus(OrderStatus.Unpaid.getCode());
                                        usingTaocanOrder.setOrderStatusDesc(OrderStatus.Unpaid.getInfo());

                                        //计算需要补交的订单超时费用
                                        //1,先获取当前景区的计时计费规则
                                        double price = usingTaocanOrder.getJishiPrice(); //计价：元/分钟
                                        overtimeFee = price * overtime;

                                        //设置订单超时费用需支付金额及当前支付状态
                                        usingTaocanOrder.setOvertimeFee(overtimeFee);
                                        usingTaocanOrder.setOvertimeFeePayStatus(PayStatus.Unpaid.getCode());
                                        usingTaocanOrder.setOvertimeFeePayStatusDesc(PayStatus.Unpaid.getInfo());

                                        sysTaocanOrderService.updateSysTaocanOrder(usingTaocanOrder);
                                        taocanOrderEndChargeMsg.setOvertimeFee(overtimeFee);

                                        //通知小程序客户端停止计费。
                                        Channel miniprogramWebsocketChannel = WebSocketChannelManager.getMiniprogramWebsocketChannelByUserId(usingTaocanOrder.getUserId());
                                        if (Objects.nonNull(miniprogramWebsocketChannel)) {
                                            WebSocketMessageHandler.notifyClientSideTaocanOrderStopCharging(miniprogramWebsocketChannel, taocanOrderEndChargeMsg);
                                        } else {
                                            log.error("当前下单用户[userId={}]与后端未连接，向该用户发送订单结束计费指令失败！", usingTaocanOrder.getUserId());
                                        }

                                        return;
                                    } else { //订单未超时或超时不足1分钟
                                    }
                                } else { //订单总计费时长不足1分钟
                                }
                                //结束并关闭订单
                                usingTaocanOrder.setOrderStatus(OrderStatus.Fulfilled.getCode());
                                usingTaocanOrder.setOrderStatusDesc(OrderStatus.Fulfilled.getInfo());

                                //此时需要支付的订单费用为0
                                overtimeFee = 0;
                                usingTaocanOrder.setOvertimeFee(overtimeFee);
                                sysTaocanOrderService.updateSysTaocanOrder(usingTaocanOrder);

                                //删除船只订单关联对象
                                userBoatOrderInfoService.deleteUserBoatOrderInfoByUserId(usingTaocanOrder.getUserId());
                                taocanOrderEndChargeMsg.setOvertimeFee(overtimeFee);

                                //通知小程序客户端停止计费。
                                Channel miniprogramWebsocketChannel = WebSocketChannelManager.getMiniprogramWebsocketChannelByUserId(usingTaocanOrder.getUserId());
                                if (Objects.nonNull(miniprogramWebsocketChannel)) {
                                    WebSocketMessageHandler.notifyClientSideTaocanOrderStopCharging(miniprogramWebsocketChannel, taocanOrderEndChargeMsg);
                                } else {
                                    log.error("当前下单用户[userId={}]与后端未连接，向该用户发送订单结束计费指令失败！", usingTaocanOrder.getUserId());
                                }

                                //订单已完成，退还押金
                                new Timer().schedule(new TimerTask() {
                                    @Override
                                    public void run() {
                                        wxPayV3Service.refund(new WxRefundReqBody(usingTaocanOrder.getOrderId(), 1, 0, "订单已完成，退还押金"));
                                    }
                                }, 5000);
                            }
                        }

                        sysBoatService.updateSysBoat(sysBoat);
                    }
                }, mockUsingTime);
            }
        }, unlockDelayTime);
    }

    /**
     * 计算计时计费订单的实际费用。
     *
     * @param jishiId
     * @param realDuration
     * @return
     */
    private double calculateJishiOrderFee(int jishiId, long realDuration) {
        SysJishi sysJishi = sysJishiService.selectSysJishiByJishiId(jishiId);
        //价格，单位：元。
        double price = sysJishi.getPrice();
        //计价单位，默认为"0"。(0: 元/分钟, 1: 元/30分钟, 2: 元/小时)
        int unit = sysJishi.getUnit();
        BigDecimal timeDecimal = null;
        if (unit == 0) {
            //将时长转换为以分钟为单位，保留2为小数。结果示例：30.45分钟
            timeDecimal = (new BigDecimal(realDuration)).divide(new BigDecimal(60 * 1000), 2, BigDecimal.ROUND_UP);
//                                            BigDecimal minuteDecimal = BigDecimal.valueOf(Double.parseDouble(String.valueOf(realDuration)) / (60 * 1000)).setScale(2, BigDecimal.ROUND_UP);
        } else if (unit == 1) {
            //将时长转换为以30分钟为单位，保留2为小数。结果示例：30.45 (30分钟)，即30.45个30分钟
            timeDecimal = (new BigDecimal(realDuration)).divide(new BigDecimal(30 * 60 * 1000), 2, BigDecimal.ROUND_UP);
        } else if (unit == 2) {
            //将时长转换为以30分钟为单位，保留2为小数。结果示例：30.45 (30分钟)，即30.45个30分钟
            timeDecimal = (new BigDecimal(realDuration)).divide(new BigDecimal(60 * 60 * 1000), 2, BigDecimal.ROUND_UP);
        }

        if (timeDecimal != null) {
            //算出订单费用，保留2为小数。费用=时长*价格
            BigDecimal consumeDecimal = timeDecimal.multiply(new BigDecimal(price)).setScale(2, BigDecimal.ROUND_UP);
            //计算出实际订单费用
            return consumeDecimal.doubleValue();
        }

        return 0;
    }

    @Data
    static class OutOrderNoRequestBody {
        /**
         * 商户系统内部订单编号。
         */
        private String outOrderNo;
    }

    @Data
    static class ConfirmBoatReqBody {
        /**
         * 船只身份识别码(出厂注册id)
         */
        private String boatIdentityId;
        /**
         * 船编号
         */
        private String boatCode;
        /**
         * 景区id
         */
        private long scenicId;
        /**
         * 码头id
         */
        private long wharfId;
        /**
         * 船只出厂日期
         */
        private String prodDate;
        /**
         * 用户的紧急联系电话号码。此号码由用户自行输入，用于在紧急情况时景区工作人员联系用户。该号码可以是用户微信绑定的手机号码，也可以不是，但必须是当前用户手机上实际使用的号码。
         */
        private String contactPhone;

    }

    @Data
    static class CancelOrderReqBody {
        private String orderId;
        private int orderType;
    }
}
